/****************************************************
          File Name:capture_encode.c
          Author: Abby Cin
          Mail: abbytsing@gmail.com
          Created Time: Mon 17 Nov 2014 08:56:57 AM CST
*****************************************************/

#include "capture_encode.h"
#include "capture_encode_define.h"

int i = 1;

static void err_exit(char *fun)
{
	syslog(LOG_ERR, "%s:%s", fun, strerror(errno));
	exit(EXIT_FAILURE);
}

static void err_info(char *info)
{
	syslog(LOG_INFO, "%s: %s", info, strerror(errno));
}

static int myioctl(int fd, int request, void *arg)
{
	int res;
	do
	{
		res = ioctl(fd, request, arg);
	}while(errno == EINTR && res == -1);
	
	return res;
}

void value_init(Encoder *value)
{
	value->yuv422_frame = (uint8_t *) malloc(width * height * 2);
	value->yuv420_frame = (uint8_t *) malloc(width *height * 3 / 2);
	
	value->nnal = -1;
	
	value->param = (x264_param_t *) malloc(sizeof(x264_param_t));
	x264_param_default(value->param); 
	x264_param_default_preset(value->param, "veryfast", "zerolatency");
	
	value->param->i_threads = 1;
	value->param->i_width = width;
	value->param->i_height = height;
	value->param->i_fps_num = 30;
	value->param->i_fps_den = 1;
	value->param->rc.i_lookahead = 0;
	value->param->i_keyint_max = 300;
	value->param->i_keyint_min = X264_KEYINT_MIN_AUTO;
	
	x264_param_apply_profile(value->param, "baseline");
	
	value->pic_in = (x264_picture_t *) malloc(sizeof(x264_picture_t));
	x264_picture_alloc(value->pic_in, X264_CSP_I420, width, height);
	value->pic_in->img.i_csp = X264_CSP_I420;
	value->pic_in->img.i_plane = 3;
	
	value->encoder = x264_encoder_open(value->param);
}

void trans_form(Encoder *value)
{
	int i, j, k =0;
	int size = width * height;
	/* get Y */
	for(i = 0; i < size; i++)
		value->yuv420_frame[i] = value->yuv422_frame[i*2];
	
	/* get U */
	for(i = 0; i < height; i++)
	{
		if((i % 2) != 0)
			continue;
		for(j = 0; j < (width / 2); j++)
		{
			if((4 * j + 1) > (2 * width))
				break;
			value->yuv420_frame[size + k * 2 * width / 4 + j] = 
			value->yuv422_frame[i * 2 * width + 4 * j +1];
		}
		k++;
	}
	
	k = 0;
	
	/* get V */
	for(i = 0; i < height; i++)
	{
		if((i % 2) == 0)
			continue;
		for(j = 0; j < (width / 2); j++)
		{
			if((4 * j + 3) > (2 * width))
				break;
			value->yuv420_frame[size + size / 4 + k * 2 * width / 4 + j] = 
			value->yuv422_frame[i * 2 * width + 4 * j + 3];
		}
		k++;
	}
}

void compress_frame(Encoder *value)
{
	value->pic_in->img.plane[0] = value->yuv420_frame;
	value->pic_in->img.plane[1] = value->pic_in->img.plane[0] + width * height;
	value->pic_in->img.plane[2] = value->pic_in->img.plane[1] + width * height / 4;
}

void encode_frame(Encoder *value)
{
	trans_form(value);
	compress_frame(value);
	
	if(x264_encoder_encode(value->encoder, &value->nals, &value->nnal, value->pic_in, &value->pic_out))
	{
		printf("\e[32m------------- write %dth frame ------------\e[0m\n",i++);
		for(value->nal = value->nals; value->nal < value->nals + value->nnal; value->nal++)
			write(value->h264_fd, value->nal->p_payload, value->nal->i_payload);
	}else
		err_exit("Encoding failed");
}

void dev_open(void)
{
	struct stat dev_stat;
	
	stat(device, &dev_stat);
	
	if(!S_ISCHR(dev_stat.st_mode))
		err_exit("EEEOR");
	
	cam_fd = open(device, O_RDWR | O_NONBLOCK);
	
	if(cam_fd == -1)
		err_exit("ERROR");
}

void dev_close(void)
{
	if(close(cam_fd) == -1)
		err_exit("close");
	
	cam_fd = -1;
}

void dev_init(void)
{
	struct v4l2_capability cap;
	struct v4l2_cropcap cropcap;
	struct v4l2_crop crop;
	struct v4l2_format format;
	struct v4l2_requestbuffers reqbuf;
	struct v4l2_streamparm parm;
	int min;
	int prot = PROT_READ | PROT_WRITE;
	
	CLEAN(cap);
	CLEAN(format);
	CLEAN(reqbuf);
	CLEAN(parm);
	
	if(myioctl(cam_fd, VIDIOC_QUERYCAP, &cap) == -1)
	{
		if(errno == EINVAL)
			err_exit("not supported by v4l2");
		else
			err_exit("VIDIOC_QUERCAP");
	}
	
	if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
		err_exit("is not support video capture:");
	
	if(!(cap.capabilities & V4L2_CAP_STREAMING))
		err_exit("%s does not support video streaming:");
	
		CLEAN(cropcap);
	
	cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	
	if(myioctl(cam_fd, VIDIOC_CROPCAP, &cropcap) == 0)
	{
		crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		crop.c = cropcap.defrect;					/* default rectangle */
		
		if(myioctl(cam_fd, VIDIOC_S_CROP, &crop) == -1)
		{
			if(errno == EINVAL)
				err_info("not support crop");
		}
	}
	else
		err_info("VIDIOC_CROPCAP");
	
	format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	format.fmt.pix.width = 640;
	format.fmt.pix.height = 480;
	format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; 		//   yuv422, my camera cannot support yuv420
	format.fmt.pix.field = V4L2_FIELD_INTERLACED;
	
	if(myioctl(cam_fd, VIDIOC_S_FMT, &format) == -1)
		err_exit("VIDIOC_S_FMT");
	
	min = format.fmt.pix.width * 2;
	if(format.fmt.pix.bytesperline < min)
		format.fmt.pix.bytesperline = min;
	min = format.fmt.pix.bytesperline * format.fmt.pix.height;
	if(format.fmt.pix.sizeimage < min)
		format.fmt.pix.sizeimage = min;
	
/*  init mmap begin */	
	reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	reqbuf.memory = V4L2_MEMORY_MMAP;
	reqbuf.count = 4;
	
	if(myioctl(cam_fd, VIDIOC_REQBUFS, &reqbuf) == -1)
	{
		if(errno == EINVAL)
			err_info("mmap-streaming is not support\t");
		else
			err_exit("VIDIOC_REQBUFS");
	}
	
	if(reqbuf.count < 2 )
		err_exit("Not enough buffer memory\n");
	
	buffers = calloc(reqbuf.count, sizeof(*buffers));
	assert(buffers != NULL );
	
	for(n_buffers = 0; n_buffers< reqbuf.count; n_buffers++)
	{
		struct v4l2_buffer buffer;
		
		CLEAN(buffer);
		buffer.type = reqbuf.type;
		buffer.memory = V4L2_MEMORY_MMAP;
		buffer.index = n_buffers;
		
		if(myioctl(cam_fd, VIDIOC_QUERYBUF, &buffer) == -1)
			err_exit("VIDIOC_QUERBUF");
		
		buffers[n_buffers].length = buffer.length;
		
		buffers[n_buffers].start = mmap(NULL, buffer.length, prot, MAP_SHARED, cam_fd, buffer.m.offset);
		
		if(buffers[n_buffers].start == MAP_FAILED)
			err_exit("mmap");
	}
/* mmap end */
}

void start_capture(void)
{
	unsigned int i;
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	
	for(i = 0; i< n_buffers; ++i)
	{
		struct v4l2_buffer buf;
		
		CLEAN(buf);
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = i;
		
		if(myioctl(cam_fd, VIDIOC_QBUF, &buf) == -1)
			err_exit("VIDIOC_QBUF");
	}
	
	if(myioctl(cam_fd, VIDIOC_STREAMON, &type) == -1)
		err_exit("Streaming on");
}

void stop_capture(void)
{
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	
	if(myioctl(cam_fd, VIDIOC_STREAMOFF, &type) == -1)
		err_exit("Streaming off");
}

int frame_read(Encoder *value)
{
	struct v4l2_buffer buf;
	
	CLEAN(buf);
	
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
	
	if(myioctl(cam_fd, VIDIOC_DQBUF, &buf) == -1)
	{
		if(errno == EAGAIN)
			return 0;
		else if(errno == EIO)
			;
		else
			err_exit("VIDIOC_DQBUF");
	}
	
	assert(buf.index < n_buffers);	
	
	memcpy(value->yuv422_frame, buffers[buf.index].start, buf.bytesused);
	
	encode_frame(value);				/* encoding */
	
	if(myioctl(cam_fd, VIDIOC_QBUF, &buf) == -1)
		err_exit("VIDIOC_QBUF");
	
	return 0;
}

void capture_process(Encoder *value)
{
	while(value->count--)
	{
		for(;;)
		{
			fd_set fds;
			struct timeval tv;
			int res;
			
			FD_ZERO(&fds);
			FD_SET(cam_fd, &fds);
			
			tv.tv_sec = 2;
			tv.tv_usec = 0;
			
			res = select(cam_fd + 1, &fds, NULL, NULL, &tv);
			
			if(res == -1)
			{
				if(errno == EINTR)
					continue;
				else
					err_exit("select");
			}
			if(res == 0)
				err_exit("select");
			else
			{
				if(frame_read(value) == 0)
					break; 				/* read frame without error */
			}
		}
	}
}

/* may never execute */
void dev_uninit(void)
{
	unsigned int i;
	
	for(i = 0; i < n_buffers; ++i)
	{
		if(munmap(buffers[i].start, buffers[i].length) == -1)
			err_exit("munmap");
	}
	
	free(buffers);
}
